/** @file oal_main.c
  *
  * @brief This file contains the oal driver funcs, includs msg/pkt tx/rx
  * transmission and general operations.
  *
  *  Copyright (C) 2024, Artosyn Technologies. ALL RIGHTS RESERVED.
  *
  *  This Information is proprietary to Artosyn Technologies and MAY NOT
  *  be copied by any method or incorporated into another program without
  *  the express written consent of Artosyn. This Information or any portion
  *  thereof remains the property of Artosyn. The Information contained herein
  *  is believed to be accurate and Artosyn assumes no responsibility or
  *  liability for its use in any way and conveys no license or title under
  *  any patent or copyright and makes no representation or warranty that this
  *  Information is free from patent or copyright infringement.
  *
***/

/****************************
 * Include
 ****************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/vmalloc.h>

#include "utils/utils.h"
#include "oal_main.h"
#include "oal_drv.h"
#include "oal_workqueue.h"
#include "oal_dnld.h"
#include "oal_mdev.h"
#include "rpc/ar_rpc.h"
#include "net/net_dev.h"

/** bus include files for init/exit API */
#ifdef AR_USB_SUPPORT
#include "bus/usb.h"
#endif

#ifdef AR_SDIO_SUPPORT
#include "bus/sdio.h"
#endif

/*****************************************************************************
 * Global variables
 *****************************************************************************/

/** Storage of the handle link to ar8030 */
static oal_handle *m_handle[MAX_OAL_ADAPTER];

/** kernel module param */
bool autopm_on = 0;

#ifdef ENABLE_PROC_FS
/** Proc system dir name */
static char *proc_dir_name = NULL;
#endif

/***************************************************************************
 * Function declared
 ***************************************************************************/
static void oal_enqueue_skbreq(IN oal_handle *poal_handle, struct list_head *q, struct ar_skbreq *req, int *counter);
static struct ar_skbreq *oal_dequeue_skbreq(IN oal_handle *poal_handle, struct list_head *q, int *counter);
static bool skbreq_tx_list_empty(IN oal_handle *poal_handle);
static bool skbreq_rx_list_empty(IN oal_handle *poal_handle);
static void oal_process_rx_req(IN oal_handle *poal_handle);
static void oal_process_tx_task(IN oal_handle *poal_handle);
static oal_status oal_recv_complete(IN oal_handle *poal_handle, IN struct ar_skbreq *req);
/*****************************************************************************
 * Functions
 *****************************************************************************/

/**
 *  @brief This workqueue function handles rx process
 *
 *  @param handle    A pointer to oal_handle
 *  @param priv      Private data
 *
 *  @return        N/A
 */
static void
oal_rx_work_func(IN oal_handle *handle, IN void *priv)
{
    unsigned long flags;
    struct ar_skbreq *req, *next;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (handle == NULL){
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        goto exit_rx_proc;
    }

    /* process rx task */
    if ((handle->state == AR_BUS_STATE_UP) || (handle->state == AR_BUS_STATE_STOP)) {
        oal_process_rx_req(handle);
    }

exit_rx_proc:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief This workqueue function handles tx process
 *
 *  @param handle    A pointer to oal_handle
 *  @param priv      Private data
 *
 *  @return        N/A
 */
static void
oal_tx_work_func(IN oal_handle *handle, IN void *priv)
{
    unsigned long flags;
    struct ar_skbreq *req, *next;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (handle == NULL){
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        goto exit_tx_proc;
    }

    /* process tx task */
    if ((handle->state == AR_BUS_STATE_UP) || (handle->state == AR_BUS_STATE_STOP)) {
        oal_process_tx_task(handle);
    }

exit_tx_proc:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief This workqueue function handles main process
 *
 *  @param handle    A pointer to oal_handle
 *  @param priv      Private data
 *
 *  @return        N/A
 */
static void
oal_main_work_func(IN oal_handle *handle, IN void *priv)
{
    unsigned long flags;
    bool result = false;
    struct ar_skbreq *req, *next;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (handle == NULL){
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        goto exit_main_proc;
    }

    if (handle->tx_workqueue == NULL){
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle->tx_workqueue is NULL!\n", __func__);
        goto exit_main_proc;
    }

    if (handle->rx_workqueue == NULL){
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle->rx_workqueue is NULL!\n", __func__);
        goto exit_main_proc;
    }

    if (!skbreq_tx_list_empty(handle)) {
        AR_DBG(DM_GENERIC, DL_VRB, "%s: trigger tx workqueue.\n", __func__);
        /* trigger tx workqueue */
        oal_queue_work(handle, handle->tx_workqueue);
    }
    else {
        AR_DBG(DM_GENERIC, DL_VRB, "%s: tx list is empty.\n", __func__);
    }

    if (!skbreq_rx_list_empty(handle)) {
        AR_DBG(DM_GENERIC, DL_VRB, "%s: trigger rx workqueue.\n", __func__);
        /* trigger rx workqueue */
        oal_queue_work(handle, handle->rx_workqueue);
    }
    else {
        AR_DBG(DM_GENERIC, DL_VRB, "%s: rx list is empty.\n", __func__);
    }

exit_main_proc:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief Wait all skbreq in workqueue process done and deinit
 *         all workqueue.
 *
 *  @param handle    A pointer to oal_handle
 *
 *  @return        N/A
 */
static void
oal_deinit_workqueue_all(IN oal_handle *handle)
{
    int loop_cnt = 0, skbs_back = 0;

    if (!handle) {
        return;
    }

    AR_DBG(DM_GENERIC, DL_INF, "%s\n", __func__);

    loop_cnt = 0;
    do {
        loop_cnt++;

        if (handle->pktin_pendcount + handle->pktin_freecount == OAL_PKTIN_NUM) {
            skbs_back = 1;
        } else {
            skbs_back = 0;
            AR_DBG(DM_GENERIC, DL_MSG, "%s: wait pktin skb (pend=%d free=%d sum %d)\n", __func__,
                    handle->pktin_pendcount, handle->pktin_freecount, OAL_PKTIN_NUM);
            oal_rx_work_func(handle, NULL);
            msleep(20);
        }
    } while((skbs_back == 0) && (loop_cnt < 10));

    loop_cnt = 0;
    do {
        loop_cnt++;

        if (handle->pktout_freecount == OAL_PTKOUT_NUM) {
            skbs_back = 1;
        } else {
            skbs_back = 0;
            AR_DBG(DM_GENERIC, DL_MSG, "%s: wait pktout skb (cur=%d)\n", __func__, handle->pktout_freecount);
            oal_process_tx_task(handle);
            msleep(20);
        }
    } while((skbs_back == 0) && (loop_cnt < 10));

    loop_cnt = 0;
    do {
        loop_cnt++;

        if (handle->msgout_freecount == OAL_MSGOUT_NUM) {
            skbs_back = 1;
        } else {
            skbs_back = 0;
            AR_DBG(DM_GENERIC, DL_MSG, "%s: wait msgout skb (cur=%d)\n", __func__, handle->msgout_freecount);
            oal_process_tx_task(handle);
            msleep(20);
        }
    } while((skbs_back == 0) && (loop_cnt < 10));

    /* Terminate main workqueue */
    if (handle->main_workqueue) {
        oal_deinit_workqueue(handle, handle->main_workqueue);
        handle->main_workqueue = NULL;
    }

    /* Terminate rx workqueue */
    if (handle->rx_workqueue) {
        oal_deinit_workqueue(handle, handle->rx_workqueue);
        handle->rx_workqueue = NULL;
    }

    /* Terminate tx workqueue */
    if (handle->tx_workqueue) {
        oal_deinit_workqueue(handle, handle->tx_workqueue);
        handle->tx_workqueue = NULL;
    }

    AR_DBG(DM_GENERIC, DL_INF, "%s done.\n", __func__);
}

/**
 *  @brief Init all workqueues and register the process func.
 *
 *  @param handle    A pointer to oal_handle
 *
 *  @return          OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
static oal_status
oal_init_workqueue_all(IN oal_handle *handle)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    oal_workqueue *main_work_queue = NULL;
    oal_workqueue *rx_work_queue = NULL;
    oal_workqueue *tx_work_queue = NULL;

    AR_DBG(DM_GENERIC, DL_MSG, "%s\n", __func__);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    /* Create workqueue for main process */
    ret = oal_init_workqueue(handle, &main_work_queue, "OAL_MAIN_WORK_QUEUE", WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, oal_main_work_func, NULL);
    if (ret != OAL_STATUS_SUCCESS) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: main workqueue init failed\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto error_init_workqueue;
    }
    handle->main_workqueue = (void*)main_work_queue;

    /* Create workqueue for tx process */
    ret = oal_init_workqueue(handle, &tx_work_queue, "OAL_TX_WORK_QUEUE", WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, oal_tx_work_func, NULL);
    if (ret != OAL_STATUS_SUCCESS) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: rx workqueue init failed\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto error_init_workqueue;
    }
    handle->tx_workqueue = (void*)tx_work_queue;

    /* Create workqueue for rx process */
    ret = oal_init_workqueue(handle, &rx_work_queue, "OAL_RX_WORK_QUEUE", WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, oal_rx_work_func, NULL);
    if (ret != OAL_STATUS_SUCCESS) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: rx workqueue init failed\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto error_init_workqueue;
    }
    handle->rx_workqueue = (void*)rx_work_queue;

    AR_DBG(DM_GENERIC, DL_INF, "%s: tx workqueue=%p rx workqueue=%p main workqueue=%p\n",
            __func__, tx_work_queue, rx_work_queue, main_work_queue);

    return ret;

error_init_workqueue:
    oal_deinit_workqueue(handle, main_work_queue);
    oal_deinit_workqueue(handle, tx_work_queue);
    oal_deinit_workqueue(handle, rx_work_queue);
    return ret;
}

/**
 *  @brief This function initializes software
 *
 *  @param handle   A pointer to oal_handle structure
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_init_sw(IN oal_handle *handle)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    poal_callbacks pcb = &handle->callbacks;
    struct ar_skbreq *req;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    /* Initialize oal_handle structure */
    init_completion(&handle->dev_init_lock);

    //TBD:power management
#if 0
    init_completion(&handle->resume_wait);
    handle->bb_suspend = false;
    handle->suspend_d_cnt = 0;
    handle->suspend_s_cnt = 0;
    handle->reset_resume_cnt = 0;
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)
    handle->cmd_start_time.tv64 = 0;
    handle->system_start_time.tv64 = 0;
    handle->suspend_start_time.tv64 = 0;
    handle->suspend_ttl_time.tv64 = 0;
    #else
    handle->cmd_start_time = 0;
    handle->system_start_time = 0;
    handle->suspend_start_time = 0;
    handle->suspend_ttl_time = 0;
    #endif
    handle->b_disconnecting = false;
#endif

    pcb = &handle->callbacks;

    handle->state = AR_BUS_STATE_DOWN;

    spin_lock_init(&handle->state_lock);

    //2. queue
    oal_init_skbreq_queues(handle);

    //3 Init work queue
    ret = oal_init_workqueue_all(handle);
    if (ret != OAL_STATUS_SUCCESS) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: init workqueue failed\n", __func__);
        goto error_init_workqueue;
    }

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return OAL_STATUS_SUCCESS;

error_init_workqueue:

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return OAL_STATUS_RESOURCE;
}

/**
 *  @brief This function frees the structure of oal_handle
 *
 *  @param handle   A pointer to oal_handle structure
 *
 *  @return         N/A
 */
static void
oal_free_oal_handle(IN oal_handle *handle)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR,  "The handle is NULL\n");
        AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
        return;
    }

    /* Free allocated memory for fwdump filename & the oal handle itself */

    //TBD
    handle->state = AR_BUS_STATE_STOP;

    //1. free workqueue
    oal_deinit_workqueue_all(handle);

    //2. free queue
    oal_deinit_skbreq_queues(handle);

    handle->state = AR_BUS_STATE_DOWN;

    /*TBD_ZY*/
    //2. MISC
    OAL_REL_SEMAPHORE(&handle->addRemoveCardSem);

    kfree(handle);

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief This function cancel all works in the queue
 *  and destroy the main workqueue.
 *
 *  @param handle    A pointer to oal_handle
 *
 *  @return        N/A
 */
static void
oal_terminate_workqueue(IN oal_handle *handle)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief skbreq rx list empty or not.
 *
 *  @param handle    A pointer to oal_handle
 *
 *  @return        Whether skbreq rx list empty or not
 */
static bool
skbreq_rx_list_empty(IN oal_handle *poal_handle)
{
    unsigned long flags;

    spin_lock_irqsave(&poal_handle->qlock, flags);

    /* rx packet and msg queue are empty */
    if (list_empty(&poal_handle->pktin_postq)) {
        spin_unlock_irqrestore(&poal_handle->qlock, flags);
        return true;
    } else {
        spin_unlock_irqrestore(&poal_handle->qlock, flags);
        return false;
    }
}

/**
 *  @brief skbreq tx list empty or not.
 *
 *  @param handle    A pointer to oal_handle
 *
 *  @return        Whether skbreq rx list empty or not
 */
static bool
skbreq_tx_list_empty(IN oal_handle *poal_handle)
{
    unsigned long flags;

    spin_lock_irqsave(&poal_handle->qlock, flags);

    /* tx packet and msg queue are empty */
    if (list_empty(&poal_handle->pktout_postq) && list_empty(&poal_handle->msgout_postq)) {
        spin_unlock_irqrestore(&poal_handle->qlock, flags);
        return true;
    } else {
        spin_unlock_irqrestore(&poal_handle->qlock, flags);
        return false;
    }
}

/**
 *  @brief Dequeue the skbreq from pktin post list and send to rx path.
 *
 *  @param poal_handle    A pointer to oal_handle
 *
 *  @return N/A
 */
static void
oal_process_rx_req(IN oal_handle *poal_handle)
{
    struct ar_skbreq *req = NULL;
    poal_callbacks pcb = &poal_handle->callbacks;

    while (true)
    {
        /* if rx list empty, quit process */
        if (skbreq_rx_list_empty(poal_handle)) {
            AR_DBG(DM_GENERIC, DL_VRB, "%s: skbreq rx list is empty\n", __func__);
            break;
        }

        /* process pkt in */
        req = oal_dequeue_skbreq_list(poal_handle, SKBREQ_LIST_PKTIN_POST);
        if (req) {
            AR_DBG(DM_GENERIC, DL_INF, "%s: process pkt in req %p\n", __func__, req);
            oal_recv_complete(poal_handle, req);
        }
    }
}

/**
 *  @brief Dequeue the skbreq from pkt/msg out post list and
 * send to tx path.
 *
 *  @param poal_handle    A pointer to oal_handle
 *
 *  @return N/A
 */
static void
oal_process_tx_task(IN oal_handle *poal_handle)
{
    int ret = 0;
    struct sk_buff *skb;
    struct ar_skbreq *req = NULL;

    if (!poal_handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: poal_handle is NULL!\n", __func__);
        return;
    }

    if (!poal_handle->callbacks.oal_write_data_sync) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: Func oal_write_data_sync is NULL!\n", __func__);
        return;
    }

    while ((req = oal_dequeue_skbreq_list(poal_handle, SKBREQ_LIST_MSGOUT_POST)) != NULL) {
        ret = poal_handle->callbacks.oal_write_data_sync(poal_handle, req);
        if (ret < 0) {
            AR_DBG(DM_TX, DL_VRB, "%s: ret=%d\n", __func__, ret);
            if (req->skb) {
                dev_kfree_skb_any(req->skb);
                req->skb = NULL;
            }
        }
    }

    while ((req = oal_dequeue_skbreq_list(poal_handle, SKBREQ_LIST_PKTOUT_POST)) != NULL) {
        ret = poal_handle->callbacks.oal_write_data_sync(poal_handle, req);
        if (ret < 0) {
            AR_DBG(DM_TX, DL_VRB, "%s: ret=%d\n", __func__, ret);
            if (req->skb) {
                dev_kfree_skb_any(req->skb);
                req->skb = NULL;
            }
        }
    }
}

/**
 *  @brief Process the rx skbreq.
 *
 *  @param poal_handle  A pointer to oal_handle
 *  @param req          skbreq for rx
 *
 *  @return             OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
static oal_status
oal_recv_complete(IN oal_handle *poal_handle, IN struct ar_skbreq *req)
{
    AR_DBG(DM_RX, DL_VRB, AR_FN_ENTRY_STR);

    if (!poal_handle) {
        return OAL_STATUS_FAILURE;
    }

    if (is_packet_type(req->type))
    {
        if (poal_handle->callbacks.oal_recv_packet_complete) {
            poal_handle->callbacks.oal_recv_packet_complete(poal_handle, req, OAL_STATUS_SUCCESS);
        }
    }

    AR_DBG(DM_RX, DL_VRB, AR_FN_LEAVE_STR);

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief Dump the data in skb
 *
 *  @param lvl      Level of the debug, refers to DL_xxx
 *  @param note     Note of the print
 *  @param skb      skb to print
 *  @param len      Length of the data to print
 *
 *  @return         N/A
 */
void
oal_dump_skb_data(uint lvl, char *note, struct sk_buff *skb, u32 len)
{
    int i;

    if (skb->len < len) {
        len = skb->len;
    }

    dump_bytes(lvl, note, skb->data, len);
}

/**
 *  @brief Deinit the specific skbreq, remove from current list
 *
 *  @param poal_handle   A pointer to oal_handle structure
 *  @param req           skbreq to deinit
 *  @param skb_free      Whether free the skb in skbreq
 *  @param count         Number of the skbreqs in list
 *
 *  @return         N/A
 */
void
oal_skbreq_deinit(IN oal_handle *poal_handle, struct ar_skbreq *req, bool skb_free, int *counter)
{
    unsigned long flags;

    spin_lock_irqsave(&poal_handle->qlock, flags);
    if (req->skb && skb_free) {
        AR_DBG(DM_GENERIC, DL_VRB, "%s: dev_kfree_skb_any skb %p\n", __func__, req->skb);
        dev_kfree_skb_any(req->skb);
        req->skb = NULL;
    }

    list_del_init(&req->list);

    if (counter)
        (*counter)--;

    spin_unlock_irqrestore(&poal_handle->qlock, flags);
}

/**
 *  @brief Alloc new skbreqs in specific list.
 *
 *  @param poal_handle   A pointer to oal_handle structure
 *  @param q             The list need to alloc new skbreq
 *  @param q_size        Number of the skbreqs that need to be alloc
 *  @param type          Type of the skbreq, refers to SKBREQ_LIST_xxx
 *
 *  @return The skbreq when alloc success, otherwise return NULL.
 */
static struct ar_skbreq *
oal_alloc_skbreq_list(IN oal_handle *poal_handle, struct list_head *q, int qsize, int type)
{
    int i;
    gfp_t flag;
    struct ar_skbreq *req, *reqs;

    flag = (in_atomic() || irqs_disabled())? GFP_ATOMIC : GFP_KERNEL;

    reqs = kcalloc(qsize, sizeof(struct ar_skbreq), flag);
    if (reqs == NULL)
        return NULL;

    req = reqs;

    for (i = 0; i < qsize; i++) {
        INIT_LIST_HEAD(&req->list);
        req->handle = poal_handle;

        /** Alloc ex data for different bus */
        if (poal_handle->callbacks.oal_alloc_skbreq_ex) {
            poal_handle->callbacks.oal_alloc_skbreq_ex(poal_handle, req);
        }

        switch (type) {
            case SKBREQ_LIST_PKTIN_FREE:
            case SKBREQ_LIST_PKTIN_PEND:
            case SKBREQ_LIST_PKTIN_POST:
                req->type = OAL_REQ_TYPE_PKTIN;
                break;
            case SKBREQ_LIST_MSGOUT_FREE:
            case SKBREQ_LIST_MSGOUT_POST:
                req->type = OAL_REQ_TYPE_MSGOUT;
                break;
            case SKBREQ_LIST_PKTOUT_FREE:
            case SKBREQ_LIST_PKTOUT_POST:
                req->type = OAL_REQ_TYPE_PKTOUT;
                break;
        }

        req->skb = NULL;

        list_add_tail(&req->list, q);

        req++;
    }

    return reqs;
}

/**
 *  @brief Deinit all skbreqs in list and free their skb.
 *
 *  @param q             The list need to deinit
 *
 *  @return N/A
 */
static void
oal_skbreq_list_deinit(struct list_head *q)
{
    struct ar_skbreq *req, *next;

    list_for_each_entry_safe(req, next, q, list) {
        req->handle = NULL;
        if (req->skb) {
            dev_kfree_skb_any(req->skb);
            req->skb = NULL;
        }

        list_del_init(&req->list);
    }
}

/**
 *  @brief Init all skbreq queues.
 *
 *  @param poal_handle     A pointer to oal_handle structure
 *
 *  @return         N/A
 */
void
oal_init_skbreq_queues(IN oal_handle *poal_handle)
{
    int pktin_free_cnt = 0;
    int pktout_free_cnt = 0;
    int msgin_free_cnt = 0;
    int msgout_free_cnt = 0;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    poal_handle->pktin_mtu = OAL_MTU_PKT_I;

    spin_lock_init(&poal_handle->qlock);
    spin_lock_init(&poal_handle->tx_qlock);
    // spin_lock_init(&poal_handle->dnld_req_lock);

    INIT_LIST_HEAD(&poal_handle->msgout_freeq);
    INIT_LIST_HEAD(&poal_handle->msgout_postq);

    INIT_LIST_HEAD(&poal_handle->pktin_freeq);
    INIT_LIST_HEAD(&poal_handle->pktin_postq);
    INIT_LIST_HEAD(&poal_handle->pktin_pendq);

    INIT_LIST_HEAD(&poal_handle->pktout_freeq);
    INIT_LIST_HEAD(&poal_handle->pktout_postq);

    msgout_free_cnt = OAL_MSGOUT_NUM;
    pktin_free_cnt = OAL_PKTIN_NUM;
    pktout_free_cnt = OAL_PTKOUT_NUM;

    poal_handle->msgout_reqs = oal_alloc_skbreq_list(poal_handle, &poal_handle->msgout_freeq, msgout_free_cnt, SKBREQ_LIST_MSGOUT_FREE);
    poal_handle->msgout_freecount = msgout_free_cnt;

    poal_handle->pktin_reqs = oal_alloc_skbreq_list(poal_handle, &poal_handle->pktin_freeq, pktin_free_cnt, SKBREQ_LIST_PKTIN_FREE);
    poal_handle->pktin_freecount = pktin_free_cnt;
    poal_handle->pktin_pendcount = 0;

    poal_handle->pktout_reqs = oal_alloc_skbreq_list(poal_handle, &poal_handle->pktout_freeq, pktout_free_cnt, SKBREQ_LIST_PKTOUT_FREE);
    poal_handle->pktout_freecount = pktout_free_cnt;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
}

/**
 *  @brief Deinit all skbreq queues.
 *
 *  @param poal_handle     A pointer to oal_handle structure
 *
 *  @return         N/A
 */
void oal_deinit_skbreq_queues(IN oal_handle *poal_handle)
{
    oal_skbreq_list_deinit(&poal_handle->msgout_freeq);
    oal_skbreq_list_deinit(&poal_handle->msgout_postq);

    oal_skbreq_list_deinit(&poal_handle->pktin_freeq);
    oal_skbreq_list_deinit(&poal_handle->pktin_postq);
    oal_skbreq_list_deinit(&poal_handle->pktin_pendq);

    oal_skbreq_list_deinit(&poal_handle->pktout_freeq);
    oal_skbreq_list_deinit(&poal_handle->pktout_postq);

    kfree(poal_handle->msgout_reqs);
    kfree(poal_handle->pktin_reqs);
    kfree(poal_handle->pktout_reqs);
}

/**
 *  @brief Enqueue skbreq at list tail.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param q                The list that skbreq add to
 *  @param req              The skbreq need to add
 *  @param count            (OUT)Number of the skbreqs in list
 *
 *  @return         N/A
 */
static void
oal_enqueue_skbreq(IN oal_handle *poal_handle, struct list_head *q, struct ar_skbreq *req, int *counter)
{
    unsigned long flags;

    spin_lock_irqsave(&poal_handle->qlock, flags);

    list_add_tail(&req->list, q);

    if (counter)
        (*counter)++;

    spin_unlock_irqrestore(&poal_handle->qlock, flags);
}

/**
 *  @brief Dequeue skbreq at list head.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param q                The list that skbreq add to
 *  @param count            (OUT)Number of the skbreqs in list
 *
 *  @return         The skbreq pop from list if success, otherwise return NULL.
 */
static struct ar_skbreq *
oal_dequeue_skbreq(IN oal_handle *poal_handle, struct list_head *q, int *counter)
{
    unsigned long flags;
    struct ar_skbreq *req;

    spin_lock_irqsave(&poal_handle->qlock, flags);

    if (list_empty(q)) {
        spin_unlock_irqrestore(&poal_handle->qlock, flags);
        return NULL;
    }

    req = list_entry(q->next, struct ar_skbreq, list);
    list_del_init(q->next);

    if (counter)
        (*counter)--;

    spin_unlock_irqrestore(&poal_handle->qlock, flags);

    return req;
}

/**
 *  @brief Enqueue skbreq at specific type of list tail.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param req              The skbreq need to add
 *  @param type             Type of the skbreq, refers to SKBREQ_LIST_xxx
 *
 *  @return         N/A
 */
void
oal_enqueue_skbreq_list(IN oal_handle *poal_handle, struct ar_skbreq *req, skbreq_list_type type)
{
    switch (type)
    {
        case SKBREQ_LIST_MSGOUT_FREE:
            oal_enqueue_skbreq(poal_handle, &poal_handle->msgout_freeq, req, &poal_handle->msgout_freecount);
            break;

        case SKBREQ_LIST_MSGOUT_POST:
            oal_enqueue_skbreq(poal_handle, &poal_handle->msgout_postq, req, NULL);
            break;

        case SKBREQ_LIST_PKTIN_FREE:
            oal_enqueue_skbreq(poal_handle, &poal_handle->pktin_freeq, req, &poal_handle->pktin_freecount);
            break;

        case SKBREQ_LIST_PKTIN_PEND:
            oal_enqueue_skbreq(poal_handle, &poal_handle->pktin_pendq, req, &poal_handle->pktin_pendcount);
            break;

        case SKBREQ_LIST_PKTIN_POST:
            oal_enqueue_skbreq(poal_handle, &poal_handle->pktin_postq, req, &poal_handle->pktin_postcount);
            break;

        case SKBREQ_LIST_PKTOUT_FREE:
            oal_enqueue_skbreq(poal_handle, &poal_handle->pktout_freeq, req, &poal_handle->pktout_freecount);
            break;

        case SKBREQ_LIST_PKTOUT_POST:
            oal_enqueue_skbreq(poal_handle, &poal_handle->pktout_postq, req, NULL);
            break;

        default:
            break;
    }
}

/**
 *  @brief Dequeue skbreq from head of specific type of list head.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param type             Type of the skbreq, refers to SKBREQ_LIST_xxx
 *
 *  @return         The skbreq pop from list if success, otherwise return NULL.
 */
struct ar_skbreq *
oal_dequeue_skbreq_list(IN oal_handle *poal_handle, skbreq_list_type type)
{
    struct ar_skbreq *req = NULL;
    switch (type)
    {
        case SKBREQ_LIST_MSGOUT_FREE:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->msgout_freeq, &poal_handle->msgout_freecount);
            break;

        case SKBREQ_LIST_MSGOUT_POST:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->msgout_postq, NULL);
            break;

        case SKBREQ_LIST_PKTIN_FREE:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->pktin_freeq, &poal_handle->pktin_freecount);
            break;

        case SKBREQ_LIST_PKTIN_PEND:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->pktin_pendq, &poal_handle->pktin_pendcount);
            break;

        case SKBREQ_LIST_PKTIN_POST:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->pktin_postq, &poal_handle->pktin_postcount);
            break;

        case SKBREQ_LIST_PKTOUT_FREE:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->pktout_freeq, &poal_handle->pktout_freecount);
            break;

        case SKBREQ_LIST_PKTOUT_POST:
            req = oal_dequeue_skbreq(poal_handle, &poal_handle->pktout_postq, NULL);
            break;

        default:
            break;
    }

    return req;
}

/**
 *  @brief Send packet req, dequeue skbreq from list pktout free
 * and push into pktout post. Then queue_work the main work queue.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param skb              skb with packet data
 *  @param priv_data        Private data for tx done
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_send_packet_req(IN oal_handle *poal_handle, struct sk_buff *skb, void *priv_data)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    oal_workqueue *tx_work_queue = NULL;
    struct ar_skbreq *req = NULL;

    if (poal_handle == NULL) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: poal_handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    /* Stop send req if handle is not up. */
    if (poal_handle->state != AR_BUS_STATE_UP)  {
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    /* send packet by pktout free list ar_skbreq */
    req = oal_dequeue_skbreq_list(poal_handle, SKBREQ_LIST_PKTOUT_FREE);
    if (!req) {
        AR_DBG(DM_GENERIC, DL_VRB, "%s: req is null\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }
    AR_DBG(DM_GENERIC, DL_VRB, "%s: get req success.\n", __func__);

    req->skb = skb;
    req->type = OAL_REQ_TYPE_PKTOUT;
    req->priv_data = priv_data;

    oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_PKTOUT_POST);

    AR_DBG(DM_GENERIC, DL_INF, "%s: trigger main_workqueue %p\n", __func__, poal_handle->main_workqueue);

    /* trigger main workqueue */
    oal_queue_work(poal_handle, poal_handle->main_workqueue);

exit:
    return ret;
}

/**
 *  @brief Send msg req, dequeue skbreq from list msgout free
 * and push into msgout post. Then queue_work the main work queue.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param skb              skb with packet data
 *  @param priv_data        Private data for tx done
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_send_msg_req(IN oal_handle *poal_handle, IN struct sk_buff *skb, IN bool is_pwr_msg)
{
    oal_status ret = OAL_STATUS_SUCCESS;
    struct ar_skbreq *req = NULL;

    if (poal_handle == NULL) {
        AR_DBG(DM_TX, DL_ERR, "%s: poal_handle is NULL!\n", __func__);
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    /* Stop send req if handle is not up. */
    if (poal_handle->state != AR_BUS_STATE_UP)  {
        ret = OAL_STATUS_FAILURE;
        goto exit;
    }

    /* send message by msgout free list ar_skbreq */
    req = oal_dequeue_skbreq_list(poal_handle, SKBREQ_LIST_MSGOUT_FREE);
    if (!req) {
        AR_DBG(DM_TX, DL_VRB, "%s: req is null\n", __func__);
        ret = OAL_STATUS_RESOURCE;
        goto exit;
    }

    AR_DBG(DM_TX, DL_VRB, "%s: get req success.\n", __func__);

    req->skb = skb;
    req->is_pwr_msg = is_pwr_msg;
    req->type = OAL_REQ_TYPE_MSGOUT;

    oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_MSGOUT_POST);

    AR_DBG(DM_GENERIC, DL_INF, "%s: trigger main_workqueue %p\n", __func__, poal_handle->main_workqueue);

    /* trigger main workqueue */
    oal_queue_work(poal_handle, poal_handle->main_workqueue);

exit:
    return ret;
}

/**
 *  @brief Send packet req complete, dequeue skbreq from list pktout post
 * and push into pktout free. Then call net device complete.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param req              skbreq that send complete
 *  @param status           status of last stage
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_send_packet_complete(IN oal_handle *poal_handle, struct ar_skbreq *req, IN oal_status status)
{
    if (!req) {
        return OAL_STATUS_FAILURE;
    }

    AR_DBG(DM_GENERIC, DL_INF,"%s, type %d\n", __func__, req->type);

    oal_skbreq_deinit(poal_handle, req, false, NULL);

    if (status == OAL_STATUS_SUCCESS) {
        if (req->skb) {
            /* Wake up the net device after processing all pkts */
            ar_net_device_tx_cb(poal_handle, req);
            dev_kfree_skb_any(req->skb);
            req->skb = NULL;
        }
    }

    oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_PKTOUT_FREE);

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief Send msg req complete, dequeue skbreq from list msgout post
 * and push into msgout free.
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *  @param req              skbreq that send complete
 *  @param status           status of last stage
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_send_msg_complete(IN oal_handle *poal_handle, struct ar_skbreq *req, IN oal_status status)
{
    if (!req) {
        return OAL_STATUS_FAILURE;
    }

    AR_DBG(DM_GENERIC, DL_INF,"%s, type %d\n", __func__, req->type);

    oal_skbreq_deinit(poal_handle, req, false, NULL);

    if (status == OAL_STATUS_SUCCESS) {
        if (req->skb) {
            dev_kfree_skb_any(req->skb);
            req->skb = NULL;
        }
    }

    oal_enqueue_skbreq_list(poal_handle, req, SKBREQ_LIST_MSGOUT_FREE);

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief Judge whether type is msg type.
 *
 *  @param type             Type of the skbreq, refers to SKBREQ_LIST_xxx
 *
 *  @return         Whether type is msg type.
 */
bool
is_message_type(u32 type)
{
    switch (type) {
        case OAL_REQ_TYPE_MSGOUT:
            return true;
    }
    return false;
}

/**
 *  @brief Judge whether type is packet type.
 *
 *  @param type             Type of the skbreq, refers to SKBREQ_LIST_xxx
 *
 *  @return         Whether type is packet type.
 */
bool
is_packet_type(u32 type)
{
    switch (type) {
        case OAL_REQ_TYPE_PKTIN:
        case OAL_REQ_TYPE_PKTOUT:
            return true;
    }
    return false;
}

/**
 *  @brief Suspend the running of the oal handle(TBD)
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_dev_suspend(IN oal_handle *poal_handle)
{
    int err = 0;

    if (poal_handle == NULL) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: poal_handle is null!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    /** TBD: stop rpc and net */
    // ar_rpc_state_stop(poal_handle->ar_rpc);

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief Resume the running of the oal handle(TBD)
 *
 *  @param poal_handle      A pointer to oal_handle structure
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_dev_resume(IN oal_handle *poal_handle)
{
    int err = 0;

    if (poal_handle == NULL) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: poal_handle is null!\n", __func__);
        return OAL_STATUS_FAILURE;
    }

    /** TBD: resume rpc and net */

    return OAL_STATUS_SUCCESS;
}

/**
 *  @brief This function init platform
 *
 *  @param handle   A pointer to oal_handle structure
 *
 *  @return         OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_init_platform(oal_handle *handle)
{
    int ret = -ENODEV;
    ar_netif_t netif = {0};
#ifdef ENABLE_PROC_FS
    char proc_name[PROC_NAME_LEN] = {0};
#endif
    oal_status status = OAL_STATUS_SUCCESS;
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is NULL!\n", __func__);
        return OAL_STATUS_COMPLETE;
    }

    /* init ar_rpc */
    handle->ar_rpc = ar_rpc_init(handle);
    if (!handle->ar_rpc) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: can't allocate ar_rpc\n", __func__);
        goto err_mem_alloc;
    }
    AR_DBG(DM_GENERIC, DL_INF, "%s: ar_rpc 0x%p\n", __func__, handle->ar_rpc);

    /* Get device handle (use mac address of ar8030 for now)*/

    /*TBD*/
#ifdef ENABLE_PROC_FS
    /* Create proc node */
    sprintf(proc_name, "dev%d", handle->handle_idx);
    ret = ar_proc_init((void*)handle, proc_name);
    if (ret != 0) {
        AR_DBG(DM_GENERIC, DL_ERR, "Create proc %s failed! ret %d.\n", proc_name, ret);
        goto proc_create_fail;
    }

    AR_DBG(DM_GENERIC, DL_MSG, "Create proc %s done.\n", proc_name);
#endif

    /** create char device class */
    sprintf(handle->chardev_class_name, "%s%d", AR_MODULE_NAME, handle->handle_idx);
#if LINUX_VERSION_CODE > KERNEL_VERSION(6, 4, 0)
    handle->chardev_class = class_create((const char*)handle->chardev_class_name);
#else
    handle->chardev_class = class_create(THIS_MODULE, (const char*)handle->chardev_class_name);
#endif
    if (IS_ERR(handle->chardev_class)) {
        AR_DBG(DM_GENERIC, DL_ERR, "Unable to allocate class!\n");
        ret = PTR_ERR(handle->chardev_class);
        goto class_create_fail;
    }

    /** Create driver char dev */
    ret = oal_mdev_init(handle);
    if (ret != 0) {
        AR_DBG(DM_GENERIC, DL_ERR, "Unable to Init drv mdev! ret %d\n", ret);
        goto mdev_create_fail;
    }

    // Create the eth device
    ret = ar_net_dev_init(handle);
    if (ret) {
        AR_DBG(DM_GENERIC, DL_ERR, AR_LOG"%s: Create net dev failed (err=%d)\n", __func__, ret);
        goto eth_init_fail;
    }

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return status;

eth_init_fail:
    oal_mdev_deinit(handle);

mdev_create_fail:
    class_destroy(handle->chardev_class);

class_create_fail:
#ifdef ENABLE_PROC_FS
    ar_proc_remove(handle);
proc_create_fail:
#endif
    ar_rpc_deinit(handle->ar_rpc);

err_mem_alloc:
    AR_ERROR(MEM_ALLOC_FAIL);
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return OAL_STATUS_FAILURE;
}

/**
 *  @brief This function deinit platform
 *
 *  @param handle       A pointer to oal_handle structure
 *
 *  @return             OAL_STATUS_SUCCESS or OAL_STATUS_FAILURE
 */
oal_status
oal_deinit_platform(oal_handle *handle)
{
    oal_status status = OAL_STATUS_SUCCESS;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: handle is null!\n", __func__);
        goto done;
    } else {
        AR_DBG(DM_GENERIC, DL_INF, "%s: handle 0x%p\n", __func__, handle);
    }

    // Deinit the eth device
    if (handle->net_dev) {
        AR_DBG(DM_GENERIC, DL_INF, "%s: net_dev 0x%p\n", __func__, handle->net_dev);
        ar_net_dev_exit(handle);
        handle->net_dev = NULL;
    }

#ifdef ENABLE_PROC_FS
    /** Deinit the proc node */
    ar_proc_remove((void*)handle);
#endif

    /** Deinit the drv mdev */
    oal_mdev_deinit(handle);

    /** Deinit the char dev class */
    class_destroy(handle->chardev_class);
    AR_DBG(DM_GENERIC, DL_INF, "%s: class_destroy done.\n", __func__);

    /* deinit ar_rpc */
    if (handle->ar_rpc) {
        AR_DBG(DM_GENERIC, DL_INF, "%s: ar_rpc 0x%p\n", __func__, handle->ar_rpc);
        ar_rpc_state_stop(handle);
        ar_rpc_deinit(handle);
        handle->ar_rpc = NULL;
    } else {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: ar_rpc is null!\n", __func__);
    }

done:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return status;
}

/**
 * @brief This function adds the card. it will probe the
 *      card, allocate the oal private and initialize the device.
 *
 *  @param card       A pointer to card
 *  @param card_mode  Refers oal_card_mode
 *
 *  @return        A pointer to oal_handle structure
 */
oal_handle *oal_add_card(void *card, struct device *dev, oal_card_mode card_mode, oal_callbacks* cb)
{
    oal_status status = OAL_STATUS_SUCCESS;
    int index = 0;
    oal_handle *handle = NULL;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    /* Allocate buffer for oal_handle */
    handle = kzalloc(sizeof(oal_handle), GFP_KERNEL);
    if (!handle) {
        AR_DBG(DM_GENERIC, DL_ERR, "Allocate buffer for oal_handle failed!\n");
        goto err_handle;
    }
    memset(handle, 0, sizeof(oal_handle));

    OAL_INIT_SEMAPHORE(&handle->addRemoveCardSem);

    if (OAL_ACQ_SEMAPHORE_BLOCK(&handle->addRemoveCardSem))
        goto exit_sem_err;

    /* Init oal_handle */
    handle->card = card;
    handle->dev = dev;
    memcpy(&handle->callbacks, cb, sizeof(oal_callbacks));

    /* Save the handle */
    for (index = 0; index < MAX_OAL_ADAPTER; index++) {
        if (m_handle[index] == NULL)
            break;
    }
    if (index < MAX_OAL_ADAPTER) {
        m_handle[index] = handle;
        handle->handle_idx = index;
        handle->card_mode = card_mode;
    } else {
        AR_DBG(DM_GENERIC, DL_ERR, "Exceeded maximum cards supported!\n");
        goto err_kmalloc;
    }

    AR_DBG(DM_GENERIC, DL_INF, "%s: m_handle[%d]->card = 0x%p\n", __func__, index, m_handle[index]->card);

    /* Update card type */
    if (handle->callbacks.oal_update_card_type) {
        handle->callbacks.oal_update_card_type(handle, card);
    }

    /* Init SW */
    if (OAL_STATUS_SUCCESS != oal_init_sw(handle)) {
        AR_DBG(DM_GENERIC, DL_ERR, "Software Init Failed\n");
        goto err_kmalloc;
    }

    /* Register the device. Fill up the private data structure with
       relevant information from the card and request for the required IRQ.
     */
    if (handle->callbacks.oal_register_dev) {
        if (handle->callbacks.oal_register_dev(handle) != OAL_STATUS_SUCCESS) {
            AR_DBG(DM_GENERIC, DL_ERR, "Failed to register device!\n");
            goto err_registerdev;
        }
    }

    if (card_mode == OAL_CARD_MODE_FW_DNLD) {
        /* Init FW and HW */
        if (OAL_STATUS_SUCCESS != oal_init_fw(handle)) {
            AR_DBG(DM_GENERIC, DL_ERR, "Firmware Init Failed\n");
            goto err_init_fw;
        }
        handle->state = AR_BUS_STATE_DL_DONE;
    }
    else if (card_mode == OAL_CARD_MODE_RPC) {
        handle->state = AR_BUS_STATE_UP;
        /* Init platform */
        if (OAL_STATUS_SUCCESS != oal_init_platform(handle)) {
            AR_DBG(DM_GENERIC, DL_ERR, "Platform Init Failed\n");
            goto err_init_platform;
        }
    }

    OAL_REL_SEMAPHORE(&handle->addRemoveCardSem);

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return handle;

err_init_platform:
    /* deinit platform */
    oal_deinit_platform(handle);

err_init_fw:
    /* Unregister device */
    AR_DBG(DM_GENERIC, DL_MSG, "unregister device\n");
    if (handle->callbacks.oal_unregister_dev) {
        handle->callbacks.oal_unregister_dev(handle);
    }

err_registerdev:
    //TBD
    oal_terminate_workqueue(handle);

err_kmalloc:
exit_sem_err:
    if (handle) {
        oal_free_oal_handle(handle);
    }

    if (index < MAX_OAL_ADAPTER) {
        m_handle[index] = NULL;
    }

err_handle:
#if 0
    OAL_REL_SEMAPHORE(&AddRemoveCardSem);
#endif
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return NULL;
}

/**
 *  @brief This function removes the card.
 *
 *  @param card    A pointer to card
 *
 *  @return        OAL_STATUS_SUCCESS
 */
oal_status
oal_remove_card(void *card)
{
    oal_handle *handle = NULL;
    oal_status status;
    int index = 0;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    AR_DBG(DM_GENERIC, DL_INF, "%s: card 0x%p\n", __func__, card);

    /* Find the correct handle */
    for (index = 0; index < MAX_OAL_ADAPTER; index++) {
        if (m_handle[index] && (m_handle[index]->card == card)) {
            AR_DBG(DM_GENERIC, DL_INF, "%s: find handle\n", __func__);
            handle = m_handle[index];
            break;
        }
    }

    if (index == MAX_OAL_ADAPTER) {
        AR_DBG(DM_GENERIC, DL_ERR, "%s: not find handle\n", __func__);
        AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
        return OAL_STATUS_SUCCESS;
    }

    if (OAL_ACQ_SEMAPHORE_BLOCK(&handle->addRemoveCardSem))
        goto exit_sem_err;

    //TBD

    if (handle->card_mode == OAL_CARD_MODE_RPC) {
        /* deinit platform */
        oal_deinit_platform(handle);
    }

    /* Unregister device */
    AR_DBG(DM_GENERIC, DL_INF, "unregister device\n");
    if (handle->callbacks.oal_unregister_dev) {
        handle->callbacks.oal_unregister_dev(handle);
    }

    /* Free handle */
    oal_free_oal_handle(handle);

    m_handle[index] = NULL;

#if 0
    OAL_REL_SEMAPHORE(&AddRemoveCardSem);
#endif

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return OAL_STATUS_SUCCESS;
exit_sem_err:
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_LEAVE_STR);
    return OAL_STATUS_PENDING;
}


/****************************
 * Kernel module func
 ****************************/
/**
 *  @brief Init function of kernel driver
 *
 *  @return         0--success otherwise failure
 */
static int __init oal_init(void)
{
    int ret = 0;

    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

#ifdef ENABLE_PROC_FS
    /* Create the proc dir */
    ar_root_proc_init(proc_dir_name);
#endif

#ifdef AR_USB_SUPPORT
    AR_DBG(DM_GENERIC, DL_INF, "[%s:%d] now init USB\n",__func__,__LINE__);

    /* register the bus */
    /** USB */
    if (usb_init() < 0) {
        AR_DBG(DM_GENERIC, DL_ERR, "usb init failed!\n");
        ret = -ENODEV;
        goto fail;
    }
#endif

#ifdef AR_SDIO_SUPPORT
    AR_DBG(DM_GENERIC, DL_INF, "[%s:%d] now init SDIO\n",__func__,__LINE__);

    /** SDIO */
    if (sdio_artosyn_init() < 0) {
        AR_DBG(DM_GENERIC, DL_ERR, "sdio init failed!\n");
        ret = -ENODEV;
        goto fail;
    }
#endif

    return 0;

fail:
#ifdef ENABLE_PROC_FS
    ar_root_proc_remove(proc_dir_name);
#endif

    return ret;
}

/**
 *  @brief Deinit function of kernel driver
 *
 *  @return         N/A
 */
static void __exit oal_exit(void)
{
    AR_DBG(DM_GENERIC, DL_INF, AR_FN_ENTRY_STR);

    /* unregister the bus */
#ifdef AR_USB_SUPPORT
    /** USB */
    AR_DBG(DM_GENERIC, DL_INF, "[%s:%d] Now calling usb exit!\n", __func__,__LINE__);
    usb_exit();
#endif

#ifdef AR_SDIO_SUPPORT
    /** SDIO */
    AR_DBG(DM_GENERIC, DL_INF, "[%s:%d] Now calling sdio exit!\n", __func__,__LINE__);
    sdio_artosyn_exit();
#endif

#ifdef ENABLE_PROC_FS
    /* Remove the proc dir */
    ar_root_proc_remove(proc_dir_name);
#endif
}

module_init(oal_init);
module_exit(oal_exit);

#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 0, 0)
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
#endif

MODULE_DESCRIPTION(AR_DRV_DESCRIPTION);
MODULE_VERSION(AR_VERS_MOD);
MODULE_AUTHOR(AR_DRV_COPYRIGHT " " AR_DRV_AUTHOR);

module_param(autopm_on, bool, 0);
MODULE_PARM_DESC(autopm_on, "Enable autopm (Default: 0)");

#ifdef ENABLE_PROC_FS
module_param(proc_dir_name, charp, 0);
MODULE_PARM_DESC(proc_dir_name, "ARTOSYN_PROC_Dir_Name");
#endif

MODULE_LICENSE("GPL");
